﻿module Physics.Chapter1.Main

open Casanova
open Casanova.Input
open Casanova.Core
open Casanova.Coroutines
open Casanova.Utilities
open Casanova.Math
open Casanova.Game
open Casanova.Drawing
open Casanova.StandardLibrary
open Casanova.StandardLibrary.Core

type [<CasanovaWorld>] World = {
  Models              : ModelLayer
  Camera              : Camera3D
  Model               : Casanova.Drawing.Model
  AvgDt               : Rule<float32<s>>
  DebugText           : Text
} with 
  static member ModelRotation'(world : World, dt : float32<s>) = 
    !world.Model.Rotation * Quaternion.CreateFromYawPitchRoll(dt * 1.0f<_>, 0.0f, 0.0f)

  static member ModelsView'(self:World, dt:float32<s>) = 
    self.Camera.View
  static member ModelsProjection'(self:World, dt:float32<s>) = 
    self.Camera.Projection

  static member AvgDt'(world:World, dt : float32<s>) =
    lerp 0.9f (!world.AvgDt) dt
  
  static member DebugTextString'(world:World, dt : float32<s>) = 
    sprintf "FPS: %3.1f\n" 
            (1.0f<s> / !world.AvgDt)


let rec start_game (args : StartGameArgs) =
  let models = ModelLayer.Create(args.GraphicsDevice, args.Content)

  let camera = Camera3D.Create(Vector3.UnitZ * 5.0f + Vector3.UnitY * 2.5f, 1.5f, args.GraphicsDevice.Viewport.AspectRatio, 0.1f, 10000.0f)

  let friction = 0.01f

  let world =
    {
      Models              = models
      Camera              = camera
      Model               = Model.Create(models, Vector3.Zero, Vector3.One * 0.5f, Microsoft.Xna.Framework.Quaternion.Identity, "cube")
      AvgDt               = Rule.Create 0.0f<s>
      DebugText           = Text.Create(args.DefaultLayer, -Vector2.UnitY * 400.0f, Vector2.One * 150.0f) }

  let inline (!) x = immediate_lookup x

  let main = yield_

  do hide_mouse_cursor()

  let input =
    [
      wait_key_press Keys.F9      => args.Save("RotatingCube")
      wait_key_press Keys.F10     => args.Load("RotatingCube")
      wait_key_down  Keys.W       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Forward * 30.0f<m/s> }
      wait_key_down  Keys.A       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Left * 30.0f<m/s> }
      wait_key_down  Keys.S       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Backward * 30.0f<m/s> }
      wait_key_down  Keys.D       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Right * 30.0f<m/s> }

      wait_mouse_move =>> 
        fun (mx,my) ->
          co{
            do world.Camera.Rotation := Vector2.Create(mx,my) * 0.1f<rad/s>
            do set_mouse_position(200,200)
          }

      wait_key_down Keys.Escape  => args.Quit

      wait_key_down Keys.X  =>
        co{
          do time_speed <- 0.01f
        }

      wait_key_down Keys.Z  =>
        co{
          do time_speed <- 1.0f
        }

    ]
  world, main, input
